/* 
   Copyright(C)2012 Fredy Ramirez www.pacificcoolwave.com
 
   This file is part of the TerraCool project.
	
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using Pcw.TerraCool;

namespace Pcw.TerraCool.Tools
{
	/// <summary>
	/// Localization xml file Tools.
	/// </summary>
	public static class LocaleFile
	{
		/// <summary>
		/// Creates a TerraCool locale file template based on .cs file.
		/// </summary>
		/// <param name='xmlLocaleFile'>Xml locale file to create.</param>
		/// <param name='sourceFiles'>Source files list.</param>
		/// <param name='overWrite'>True if we want to overwrite an existing xml file.</param>
		public static void CreateLocaleFile(SourceType sourceType, string xmlLocaleFile, 
		                                    IEnumerable<string> sourceFiles, bool overWrite) 
		{
			Dictionary<string, CatalogData> data = new Dictionary<string, CatalogData>();
		
			bool xmlExists = File.Exists(xmlLocaleFile);
			if (xmlExists && !overWrite)
				throw new InvalidOperationException("Xml locale file '" + xmlLocaleFile + "' already exists.");

			foreach(string file in sourceFiles)
			{
				if (!File.Exists(file))
					throw new FileNotFoundException("Source file: '" + file + "' does not exist.");

				GetData(file, data);
			}

			XmlDocument xDoc = new XmlDocument();
			XmlNode docNode = xDoc.CreateXmlDeclaration("1.0", "UTF-8", null);
			xDoc.AppendChild(docNode);

			XmlComment comment = xDoc.CreateComment(HeadComment(xmlLocaleFile)); 
			xDoc.AppendChild(comment);

			XmlNode terraCoolNode = xDoc.CreateElement ("TerraCool");
			xDoc.AppendChild(terraCoolNode);

			XmlNode head = AddElement(xDoc, "Header", terraCoolNode, null);
			AddElement(xDoc, "Author", head, "The translator name");
			AddElement(xDoc, "Url", head, "Web site to visit");
			AddElement(xDoc, "Type", head, sourceType.ToString());
			AddElement(xDoc, "Locale", head, "Locale code");

			XmlNode strings = AddElement(xDoc, "Strings", terraCoolNode, null);

			foreach (KeyValuePair<string, CatalogData> pair in data)
			{
				XmlNode str = AddElement(xDoc, "String", strings, null);
				AddAttribute(xDoc, "id", str, pair.Key);
				AddAttribute(xDoc, "msg", str, "");
				if (pair.Value.IdPlural != null) {
					AddAttribute(xDoc, "id_plural", str, pair.Value.IdPlural);
					AddAttribute(xDoc, "msg_plural", str, "");
				}
			}

			xDoc.Save(xmlLocaleFile);
		}

		public static void CreateLocaleFile(SourceType sourceType, string xmlLocaleFile, string sourceFile, bool overWrite) 
		{
			CreateLocaleFile(sourceType, xmlLocaleFile, GetFileAsList(sourceFile), overWrite);
		}

		/// <summary>
		/// Gets the file full path.
		/// </summary>
		/// <returns>
		/// The file full path..
		/// </returns>
		/// <param name='file'>
		/// File.
		/// </param>
		/// <param name='extension'>
		/// Extension to verify. If extension != null then this is appended to the name of file.
		/// </param>
		/// <param name='defaultDir'>
		/// Default dir to use if directory name of file can not be get.
		/// </param>
		/// <param name='verifyExistence'>
		/// Verify existence. If this parameter is true and file does not exist, returns an empty string.
		/// </param>
		public static string GetFileFullPath(string file, string extension, string defaultDir, bool verifyExistence) 
		{
			if (extension != null)
				if(!file.EndsWith("." + extension))
					file = file + "." + extension;
			if (String.IsNullOrEmpty(Path.GetDirectoryName(file)))
				file = Path.Combine(defaultDir, file);

			if (verifyExistence) 
				if(!File.Exists(file))
					return String.Empty;

			return file;
		}

		private static XmlNode AddElement(XmlDocument xDoc, string name, XmlNode parent, string value) 
		{
			XmlNode node = xDoc.CreateElement (name);
			parent.AppendChild(node);
			if (value != null)
				node.InnerText = value;
			return node;
		}

		private static XmlAttribute AddAttribute (XmlDocument xDoc, string name, XmlNode parent, string value) 
		{
			XmlAttribute att = xDoc.CreateAttribute(name);
			if (value == null)
				value = "";
			att.Value = value;
			parent.Attributes.Append(att);
			return att;
		}

		private static List<string> GetFileAsList(string file)
		{
			List<string> f = new List<string>();
			f.Add(file);
			return f;
		}

		private static void GetData(string sourceFile, Dictionary<string, CatalogData> data)
		{
			//Console.WriteLine("GetIds - sourceFile: " + sourceFile);

			string src = null;
			using (StreamReader fs = new StreamReader(sourceFile)){
				src = GetSourceFromStream(fs);
			}

			if (src == null)
				return;

			string stringPattern = @"\.Get(Plural){0,1}String {0,}\( {0,}@{0,1}""";
			//Console.WriteLine("stringPattern: " + stringPattern);

			RegexOptions options = new RegexOptions();
			options |= RegexOptions.Singleline;
			options |= RegexOptions.Multiline;

			Regex regex = new Regex(stringPattern, options);

			if (!regex.IsMatch(src))
				return;

			//Console.WriteLine("regex.IsMatch(src) = True");

			Match[] match = FindSubstrings(regex, src);
			if (match == null)
				return;

			//Console.WriteLine("MatchLength: " + match.Length);

			string id = null;
			string id_plural = null;
			int endIndex = 0;

			for (int x = 0; x < match.Length; x++) {
				id = null;
				id_plural = null;

				string func = match.GetValue(x).ToString();
				//Console.WriteLine("func: {0}", func);

				id = GetNextString(match[x].Index + func.Length - 2, src, ref endIndex);
				if (func.StartsWith(".GetPluralString")) 
					id_plural = GetNextString(endIndex + 1, src, ref endIndex);

				if (data.ContainsKey(id))
					data[id].IdPlural = id_plural;
				else
					data.Add(id, new CatalogData(null, id_plural, null));
			}

		}

		private static string GetSourceFromStream(TextReader fs) 
		{
			string src = null;
			try
			{
				src = fs.ReadToEnd();
			}
			catch
			{
				return null;
			}
			finally
			{
				if (fs != null)
					fs.Close();
			}
			return src;
		}

		private static Match[] FindSubstrings(Regex regex, string source)
		{
			Match[] retArray = null;
			
			MatchCollection theMatches = regex.Matches(source);
	
			retArray = new Match[theMatches.Count];
			theMatches.CopyTo(retArray, 0);

			return retArray;
		}

		private static string GetNextString (int baseIndex, string src, ref int endIndex) 
		{
			List<string> str = new List<string>(); // Concatenated strings

			str.Add(GetSingleString(baseIndex, src, ref endIndex));

			int concat = IsConcatenated(src, endIndex);
			while(concat > -1) {
				endIndex = concat;
				str.Add(GetSingleString(endIndex, src, ref endIndex));
				concat = IsConcatenated(src, endIndex);
			}

			string result = null;
			foreach(string s in str)
				result = result + s;

			return result;
		}

		private static string GetSingleString(int baseIndex, string src, ref int endIndex) 
		{
			bool isVerbatim = false;
			int quoteCount = 1;
			int startIndex = src.Substring(baseIndex).IndexOf('\"') + baseIndex + 1;
			if (src[startIndex - 2] == '@')
				isVerbatim = true;

			//Console.WriteLine("isVerbatim: {0}", isVerbatim);
			//Console.WriteLine(" startIndex: " + startIndex);

			endIndex = 0;
			int findEndIndex = startIndex;
			while (true) {
				endIndex = src.Substring(findEndIndex + 1).IndexOf('\"') + findEndIndex + 1;
				//Console.WriteLine(" endIndex: " + endIndex);
				//Console.WriteLine("Quote found: {0} in index: {1} - findIndex: {2}", src[endIndex], endIndex, findEndIndex);

				quoteCount++;
				//Console.WriteLine("quoteCount: {0}", quoteCount);

				if ((isVerbatim && src[endIndex + 1] != '\"' && quoteCount % 2 == 0) || 
				    (!isVerbatim && src[endIndex - 1] != '\\')) {
					break; // The " is not escaped
				}

				findEndIndex = endIndex;
			}

			string result = src.Substring(startIndex, endIndex - startIndex);
			//Console.WriteLine("String found: {0}", result);

			if (isVerbatim) {
				result = result.Replace("\"\"", "\""); 
				result = EscapeUnicode(result);
			}

			return result;
		}

		// Return the index of the next string after +
		// If it is not concatenated returns -1
		private static int IsConcatenated(string src, int endIndex) 
		{
			while(true) {
				endIndex++;
				if (src[endIndex] == ' ' || src[endIndex] == '\n' || src[endIndex] == '\r') 
					continue;

				if (src[endIndex] == '+') {
					while(true) {
						endIndex++;
						if (src[endIndex] == '\"')
							return endIndex;
					}
				}
				else {
					return -1;
				}
			}
		}

		private static string HeadComment(string name)
		{
			name = Path.GetFileName(name);
			return 
			"=================================================================\n" + 
			"  Localization file: " + name + "\n\n" +
			"  Automatically created by TerraCool. \n" +
			"  Copyright(C)2012 Fredy Ramirez - www.pacificcoolwave.com \n\n" +
			"  Creation date: " +  DateTime.Today.ToString("yyyy-MM-dd") + " \n" +
			"=================================================================";
		}


		public static string EscapeUnicode(string input)
		{
			string result = input;
			result = result.Replace("\\", @"\\"); 
			result = result.Replace("\"", @"\""");
			result = result.Replace("\a", @"\a");
			result = result.Replace("\b", @"\b");
			result = result.Replace("\f", @"\f");
			result = result.Replace("\n", @"\n");
			result = result.Replace("\r", @"\r");
			result = result.Replace("\t", @"\t");
			result = result.Replace("\v", @"\v");
			result = result.Replace("\0", @"\0");

			StringBuilder builder = new StringBuilder();

			for (int i = 0; i < result.Length; i++)
			{
				if (char.IsSurrogatePair(result, i)) {
					builder.Append("\\U" + char.ConvertToUtf32(result, i).ToString("X8"));
					i++;  //skip the next char    
				}
				else
				{
					int charVal = char.ConvertToUtf32(result, i);
					if (charVal > 127) {
						builder.Append("\\u" + charVal.ToString("X4"));
					}
					else {
						//an ASCII character
						builder.Append(result[i]);
					}
				}
			}

			result = builder.ToString();
			//Console.WriteLine(" Escape result: {0}", result);
			return result;
		}

	}
}

